/*
* Copyright (c) 2010 Michael Collins
*
* This file is part of TerraFirma.
*
* TerraFirma is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* TerraFirma is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with TerraFirma.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef INPUT_THREAD_H
#define INPUT_THREAD_H

#include "task_thread.h"
#include "mpi.h"
#include <map>
#include <SFML/Window.hpp>

#include <boost/archive/xml_iarchive.hpp>
#include <boost/archive/xml_oarchive.hpp>
#include <boost/serialization/split_member.hpp>
#include <boost/serialization/nvp.hpp>
#include "message_input.h"

#define PRIMARY 0
#define SECONDARY 1
#define TERTIARY 2
#define S16_MAX 0x7fff

namespace engine
{
    typedef uint8_t control_idx; //Enumerates which control changed in update messages

    //template<typename T = control_msg>
    class input_thread : public task_thread<control_msg>
    {
    public:
        input_thread() :
                         task_thread<control_msg>(name()),
                         mouse_rule(NULL), wheel_up(NULL),
                         wheel_down(NULL), running(true),
                         has_focus(true), text_box(NULL)
        {
            //MPI.add_listener(msg_queue.ref(), tid);
            rules.reserve(32);
        }
        ~input_thread();
        const char* name()
        {
            return "input_thread";
        }
        //const char* name() {return "input_thread";}
        void F(const boost::thread::id tid);

        //****Control state structures****//
        enum control_type {STATE_BOOLEAN, STATE_1D, STATE_2D};

        //Control that is either on or off
        struct boolean
        {
            uint8_t type, idx;
            uint8_t state; //0 for off, non-zero for on
        };

        //Control that throttles a single 16-bit signed value
        struct stick_1d
        {
            uint8_t type, idx;
            int16_t state, delt;
        };

        //'2D' analog control (like a mouse or joystick) with an x and y axis
        struct stick_2d
        {
            uint8_t type, idx;
            int16_t state_x, state_y;
            int16_t delt_x, delt_y;
        };

        //All-encompassing union to abstract the specific type away
        struct control_state
        {
            union
            {
                uint8_t type;
                boolean st_b;
                stick_1d st_1d;
                stick_2d st_2d;
            };
private :
            friend class boost::serialization::access;
            template<class Archive>
            void serialize(Archive& ar, unsigned int version)
            {
                ar & BOOST_SERIALIZATION_NVP(type);
                ar & BOOST_SERIALIZATION_NVP(st_b.idx);

                //Zero out the rest of the struct
                //st_2d.state_x = st_2d.state_y = st_2d.delt_x = st_2d.delt_y = 0;
            }
        };


        //****Control modifiers****//
        //Describes how a specific input event changes a control
        enum control_modifier_type {BUTN_MOM, BUTN_TOG, BUTN_CHG, BUTN_UP, BUTN_DWN, BUTN_LFT, BUTN_RGT, //Button types
                                    JOYSTICK_X, JOYSTICK_Y, JOYSTICK_INV_Y, //Axis that the joystick axis maps to
                                    MOUSE, MOUSE_INV
                                   }; //Mouse controls x and y (y can be inverted)

        /*Control rule defines how this certain action will change the control states*/
        struct control_rule
        {
            char name [32]; //Name like "jump" or "Move forward"
            control_state* state; //Pointer to the state that this modifies
            control_modifier_type type; //How this will behave and change the state of the control
            float sensitivity; //For 1D and 2D control states, multiplier on how much it changes
            sf::Event Primary, Secondary; //Reflexive, mostly so we can print out what keys are bound to a control
private:
            friend class boost::serialization::access;
            template<class Archive>
            void save(Archive& ar, const unsigned int version) const
            {
                ar & BOOST_SERIALIZATION_NVP(name);
                ar & BOOST_SERIALIZATION_NVP(type);
                ar & BOOST_SERIALIZATION_NVP(sensitivity);
                ar & BOOST_SERIALIZATION_NVP(Primary);
                ar & BOOST_SERIALIZATION_NVP(Secondary);
                {
                    //Enumerate the pointer as an index
                    unsigned int offs = state->st_b.idx;
                    ar & BOOST_SERIALIZATION_NVP(offs);
                }
            }
            template<class Archive>
            void load(Archive& ar, const unsigned int version)
            {
                ar & BOOST_SERIALIZATION_NVP(name);
                ar & BOOST_SERIALIZATION_NVP(type);
                ar & BOOST_SERIALIZATION_NVP(sensitivity);
                ar & BOOST_SERIALIZATION_NVP(Primary);
                ar & BOOST_SERIALIZATION_NVP(Secondary);
                {
                    unsigned int offs;
                    ar & BOOST_SERIALIZATION_NVP(offs);
                    state = &(states[offs]); //This is the only reason states needs static linking
                }
            }
            BOOST_SERIALIZATION_SPLIT_MEMBER()
        };

        struct control_rule_ref
        {
            control_rule_ref() : rule(NULL) {}
            control_rule_ref(control_rule* r) : rule(r) {}
            control_rule_ref(control_rule& r) : rule(&r) {}

            //unary operator for dereference
            inline control_rule& operator* ()
            {
                return *rule;
            }

            inline operator control_rule*()
            {
                return rule;
            }
            inline operator control_rule&()
            {
                return *rule;
            }
            inline operator bool ()
            {
                return (rule != NULL);
            }

            control_rule* rule;
private:
            friend class boost::serialization::access;
            template <class Archive>
            void save(Archive& ar, const unsigned int version) const
            {
                size_t offs;
                if (!rule)
                    //Null
                    offs = 0xFFFFFFFF;
                else
                    offs = (size_t)(rule - (&rules[0]));
                ar & BOOST_SERIALIZATION_NVP(offs);
            }

            template <class Archive>
            void load(Archive& ar, const unsigned int version)
            {
                size_t offs;
                ar & BOOST_SERIALIZATION_NVP(offs);
                if (offs < rules.size())
                    rule = &(rules[offs]);
                else
                    rule = NULL;
            }
            BOOST_SERIALIZATION_SPLIT_MEMBER()
        };


        /** Manipulation functions -- Alter on a per-mod basis how control works **/
        //Adds num number of control states to the pool of available control state
        //Returns index of first of set
        control_idx add_boolean_state(control_idx num);
        control_idx add_1d_state(control_idx num);
        control_idx add_2d_state(control_idx num);

        //Adds a control rule to the pool of control rules. Give it the control index of the state it modifies
        //Returns false if arguments did not pass sanity check, in which case pool is unmodified
        bool add_control_rule(const char* name, control_idx idx, control_modifier_type type, float sens = 1.0f);
        bool add_control_rule(const char* name, control_idx idx, control_modifier_type type, float sens, const sf::Event& dflt);

        //Determines the pointer to a control rule of the given name, pointer to return value in argument
        //If rule==NULL, just return the existence of the named rule
        bool get_control_rule(const char* name, control_rule** rule);
        //Determines the pointer to a control rule mapped to the input event.
        bool get_control_rule(const sf::Event& ev, control_rule** rule);

        //Binds the given event (pending it actually being a valid control event) to the control event
        //pointed to by rule. Slot indicates if this is the primary or secondary bind for that control
        bool bind_control(const sf::Event& ev, control_rule* rule, uint8_t slot = PRIMARY);
        bool unbind_control(const sf::Event& ev); //Finds the rule bound to ev, and removes the binding

        //Save and load fixed rules to a file (These should be fixed across all hosts running the same game)
        void save_fixed(boost::archive::xml_oarchive& oa) const;
        void load_fixed(boost::archive::xml_iarchive& ia);

        //Save and load event bindings to a file. Bindings are expected to change all the fucking time
        void save_bindings(boost::archive::xml_oarchive& oa) const;
        void load_bindings(boost::archive::xml_iarchive& ia);

    protected:
    private:
        bool events_pending();
        void add_control_rule(const control_state);

        void process_message(const control_msg& msg);
        void process_event(const sf::Event& ev);

        //We want to be able to save and load control mappings
        friend class boost::serialization::access;
        //template<class Archive>
        //void serialize(Archive& ar, const unsigned int version);

        //Containers
        static std::vector<control_state> states;
        static std::vector<control_rule> rules;

        //typedef control_rule* rule_ptr;

        std::map<sf::Key::Code, control_rule_ref> keymap;          //Maps keys to control rules
        std::map<sf::Mouse::Button, control_rule_ref> mousebtnmap; //Maps mouse buttons to control rules
        std::map<unsigned int, control_rule_ref> joybtnmap;        //Maps gamepad buttons to control rules
        std::map<sf::Joy::Axis, control_rule_ref> joyaxismap;      //Maps gamepad stick axis to control rules
        control_rule_ref mouse_rule, wheel_up, wheel_down;         //Maps mouse movement to control rule

        /** Runtime operation shit **/
        void bool_event(const control_rule& ctrl, bool keystate);
        void mouse_event(const control_rule& ctrl, const sf::Event::MouseMoveEvent& ev);
        void joyaxis_event(const control_rule& ctrl, float pos);
        void text_event(const sf::Event::TextEvent ev);
        bool running, has_focus;
        void* text_box; //Pointer to the text input field, if any, null if not in text mode
    };
};

#endif // INPUT_THREAD_H
